package oop.ex3.nameserver;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;

import oop.ex3.messages.*;
import oop.ex3.messages.exception.InvalidHeaderException;
import oop.ex3.messages.exception.UnexpectedMessageEndException;

import oop.ex3.nameserver.actions.NsAction;
import oop.ex3.nameserver.exception.UnexpectedMessageException;
import oop.ex3.nameserver.exception.UnhandledMessageException;
import oop.ex3.sockets.ClientSocket;

public class SessionHandler implements Runnable {
		
	ClientSocket _socket;
	HashMap<String, NsAction> _actions;
	NSContext _context;
	
	
	public SessionHandler(Socket s, 
			HashMap<String,NsAction> actions, NSContext context ) 
	throws UnknownHostException, IOException{
		
		_context = context;
		_socket = new ClientSocket(s);
		_actions = actions;
	}

	@Override
	public void run() {
		
		try {
			MessageInterface msg = _socket.readMessage();
			if (!msg.getHeader().equals(ValidHeaders.BEGIN)){
				throw new UnexpectedMessageException();
			}	
			performHandshake((PeerInfoMessage)msg);
			
			boolean _isGoodBye = false;

			while (!(msg = _socket.readMessage()).getHeader().equals
					(ValidHeaders.ENDSESSION)){
				if (!_actions.containsKey(msg.getHeader())){
					throw new UnhandledMessageException();
				} 
				//FM sent goodbye and than sent a message which is not 
				//endsession
				else if  (_isGoodBye){
					throw new UnexpectedMessageException();
				}
				else{
					if (msg.getHeader().equals(ValidHeaders.GOODBYE)){
						_isGoodBye = true;
					}
					_actions.get(msg.getHeader()).run(_socket, msg);
				}
			}
			_socket.SendMessage(new SimpleMessage(ValidHeaders.DONE));
			_socket.closeClientSocket();
			
			//TODO unhandled IOException, maby you should change this  to Exception e
		} catch (UnexpectedMessageException e){
			e.printStackTrace();
			try {
				_socket.SendMessage(new SimpleMessage(ValidHeaders.ERROR));
				_socket.closeClientSocket();
				//TODO if error message throws IO, you don't close the socket, change it!!!
			} catch (IOException e1){
				//empty catch block- no need to handle that type of exception
				//in that case
			} catch (InvalidHeaderException e2) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
		} catch (IOException e1){
			e1.printStackTrace();
			try {
				_socket.SendMessage(new SimpleMessage(ValidHeaders.ERROR));
				_socket.closeClientSocket();
			} catch (IOException e2){
				//empty catch block- no need to handle that type of exception
				//in that case
			} catch (InvalidHeaderException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		catch (UnhandledMessageException e3){
			e3.printStackTrace();
			try {
				_socket.SendMessage(new SimpleMessage(ValidHeaders.ERROR));
				_socket.closeClientSocket();
			} catch (IOException e2){
				//empty catch block- no need to handle that type of exception
				//in that case
			} catch (InvalidHeaderException e2) {
				// TODO Auto-generated catch block
			}
		} catch (UnexpectedMessageEndException e) {
			e.printStackTrace();
			try {
				_socket.SendMessage(new SimpleMessage(ValidHeaders.ERROR));
				_socket.closeClientSocket();
			} catch (IOException e2){
				//empty catch block- no need to handle that type of exception
				//in that case
			} catch (InvalidHeaderException e1) {
				//TODO this never happens
			}
		} catch (InvalidHeaderException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	//TODO you should check what you want to do with IOExceptions here
	/**
	 * @throws UnexpectedMessageEndException 
	 * @throws InvalidHeaderException 
	 * 
	 */
	private void performHandshake(PeerInfoMessage msg)
	throws IOException, UnexpectedMessageException, 
	UnexpectedMessageEndException, InvalidHeaderException {
		InetSocketAddress fm = msg.getPeer();
		if (_context.fmContains(fm)){
			_socket.SendMessage(new SimpleMessage(ValidHeaders.DONE));
		}
		//unKnownFm
		else {
			System.out.println("fm empty: "+_context.fmIsEmpty());
			_socket.SendMessage(new SimpleMessage(ValidHeaders.WELCOME));
			MessageInterface recievedMsg;
			//get files
			ArrayList<String> files = new ArrayList<String>();
			FileMessage recievedMsg1;
			while (!(recievedMsg = _socket.readMessage()).getHeader()
					.equals(ValidHeaders.ENDLIST)){
				if (!recievedMsg.getHeader().equals(ValidHeaders.CONTAINFILE)){
					throw new UnexpectedMessageException();
				} 
				recievedMsg1 = (FileMessage)recievedMsg;
				files.add(recievedMsg1.getFileName());
				NsAction.sendDone(_socket);
			}
			NsAction.sendDone(_socket);
			//get Servers
			//TODO maybe set?
			ArrayList<InetSocketAddress> servers = 
				new ArrayList<InetSocketAddress>();
			PeerInfoMessage recievedMsg2;
			while (!(recievedMsg = _socket.readMessage()).getHeader()
					.equals(ValidHeaders.ENDLIST)){
				if (!recievedMsg.getHeader().
						equals(ValidHeaders.CONTAINNAMESERVER)){
					throw new UnexpectedMessageException();
				} 
				recievedMsg2 = (PeerInfoMessage)recievedMsg;
				servers.add(recievedMsg2.getPeer());
				NsAction.sendDone(_socket);
			}  
			NsAction.sendDone(_socket);
			_context.addNS(servers);
			for (String file: files){
				_context.addFile(file, fm);
			}
		}
		
	}

}
